package com.denlaku.longan.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author tianx
 */
public class FileUtil {
    private static final int BUFFER_SIZE = 8192;

    private static final long MAX_READ_SIZE = 1024 * 1024 * 1024 * 100L;

    public static boolean delete(File file) {
        if (file == null || !file.exists()) {
            return false;
        }
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f: files) {
                    delete(f);
                }
            }
        }
        return file.delete();
    }

    /**
     * 解压
     *
     * @param zipInputStream zip流
     * @param outFile 输出文件
     */
    public static void unzip(ZipInputStream zipInputStream, File outFile) {
        try {
            ZipEntry zipEntry;
            File outItemFile;
            while (null != (zipEntry = zipInputStream.getNextEntry())) {
                outItemFile = new File(outFile, zipEntry.getName());
                // FileUtil.file会检查slip漏洞，漏洞说明见http://blog.nsfocus.net/zip-slip-2/
                FileUtil.isSlip(outFile, outItemFile);
                if (zipEntry.isDirectory()) {
                    // 目录
                    if (!outItemFile.mkdirs()) {
                        throw new RuntimeException("Failed to mkdir");
                    }
                } else {
                    // 文件
                    FileUtil.copy(zipInputStream, toOutputStream(outItemFile), MAX_READ_SIZE);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 检查父完整路径是否为自路径的前半部分，如果不是说明不是子路径，可能存在slip注入
     * 见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param parentFile 父文件或目录
     * @param file       子文件或目录
     * @return 是否存在slip注入
     */
    public static boolean isSlip(File parentFile, File file) {
        if (null != parentFile && null != file) {
            String parentCanonicalPath;
            String canonicalPath;
            try {
                parentCanonicalPath = parentFile.getCanonicalPath();
                canonicalPath = file.getCanonicalPath();
                if (!canonicalPath.startsWith(parentCanonicalPath)) {
                    return true;
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }

    public static long copy(InputStream source, OutputStream sink, long limit) throws IOException {
        long nRead = 0L;
        long total = 0;
        byte[] buf = new byte[BUFFER_SIZE];
        int n;
        while ((n = source.read(buf)) > 0) {
            total += n;
            if (total > limit) {
                throw new RuntimeException("The number of bytes read exceeded the limit");
            }
            sink.write(buf, 0, n);
            nRead += n;
        }
        return nRead;
    }

    public static OutputStream toOutputStream(File file) throws FileNotFoundException {
        return new FileOutputStream(file);
    }

    private FileUtil() {}
}
